import {
  Body,
  ConflictException,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  UploadedFile,
  UseGuards,
  UseInterceptors
} from '@nestjs/common'
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'
import { User } from 'src/users/entities/user.entity'
import { UsersService } from './users.service'
import { UpdateUserDto } from './dto/update-user.dto'
import { CurrentUser } from 'src/common/user.decorator'
import { ProfilesService } from './profiles.service'
import { FileInterceptor } from '@nestjs/platform-express'
import { Express } from 'express'
import { extname } from 'path'
import { diskStorage } from 'multer'
import { randomUUID } from 'crypto'
import { InjectRepository } from '@nestjs/typeorm'
import { Favorite } from './entities/favorite.entity'
import { Repository } from 'typeorm'
import { History } from './entities/history.entity'
import { Address } from './entities/address.entity'
import { CreateAddressDto } from './dto/create-address.dto'
import { UpdateAddressDto } from './dto/update-address.dto'

@UseGuards(JwtAuthGuard)
@Controller('api/me')
export class MeController {
  constructor(
    private readonly usersService: UsersService,
    private readonly profilesService: ProfilesService,
    // TODO: cleanup
    @InjectRepository(Favorite)
    public favoritesRepository: Repository<Favorite>,
    @InjectRepository(History)
    public historiesRepository: Repository<History>,
    @InjectRepository(Address)
    public addressesRepository: Repository<Address>
  ) {}

  @Get()
  async findOne(@CurrentUser() user: User) {
    return user
  }

  @Patch()
  async updateOne(@CurrentUser() user: User, @Body() dto: UpdateUserDto) {
    if (dto.profile && user.profile != null) {
      dto.profile = { ...user.profile, ...dto.profile }

      // TODO: 实现 @IsUnique 验证器
      if (
        dto.profile?.nickname &&
        user.profile.nickname !== dto.profile.nickname
      ) {
        const found = await this.profilesService.findOne({
          where: { nickname: dto.profile.nickname }
        })
        if (found) {
          throw new ConflictException('此昵称已被注册')
        }
      }
    }
    return await this.usersService.updateOneSimple(user.id, dto)
  }

  @Post('avatar')
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: './public/uploads/profiles',
        filename: (req, file, cb) => {
          // TODO: 使用 SHA-1 hash 作为文件名
          const filename = randomUUID() + extname(file.originalname)
          return cb(null, filename)
        }
      })
    })
  )
  async uploadAvatar(
    @CurrentUser() user: User,
    @UploadedFile() file: Express.Multer.File
  ) {
    const profile = { ...user.profile, avatar: file.path }
    await this.profilesService.updateOneSimple(profile.id, profile)
    return await this.usersService.findOne(user.id)
  }

  @Get('favorites')
  async getAllFavorites(@CurrentUser() user: User) {
    return await this.favoritesRepository
      .createQueryBuilder('favorite')
      .where({
        favorite: true,
        user: {
          id: user.id
        }
      })
      .select(['item', 'favorite'])
      .leftJoin('favorite.item', 'item')
      .leftJoin('favorite.user', 'user')
      .getMany()
  }

  @Get('histories')
  async getAllHistories(@CurrentUser() user: User) {
    return await this.historiesRepository
      .createQueryBuilder('history')
      .where({
        user: {
          id: user.id
        }
      })
      .select(['item', 'history'])
      .leftJoin('history.item', 'item')
      .leftJoin('history.user', 'user')
      .getMany()
  }

  @Get('addresses')
  async getAllAddresses(@CurrentUser() user: User) {
    return await this.addressesRepository.find({
      where: {
        user: {
          id: user.id
        }
      }
    })
  }

  @Post('addresses')
  async createOneAddress(
    @Body() dto: CreateAddressDto,
    @CurrentUser() user: User
  ) {
    return await this.addressesRepository.save({
      ...dto,
      user
    })
  }

  @Patch('addresses/:id')
  async updateOneAddress(
    @Param('id') id: string,
    @Body() dto: UpdateAddressDto,
    @CurrentUser() user: User
  ) {
    await this.addressesRepository.findOneOrFail({
      where: {
        id,
        user: {
          id: user.id
        }
      }
    })

    return await this.addressesRepository.save({
      id,
      ...dto
    })
  }

  @Delete('addresses/:id')
  async deleteOneAddress(@Param('id') id: string, @CurrentUser() user: User) {
    const address = await this.addressesRepository.findOneOrFail({
      where: {
        id,
        user: {
          id: user.id
        }
      }
    })

    return await this.addressesRepository.softRemove(address)
  }
}
